package binding

import (
	"crypto/tls"
	"mime/multipart"
	"net/http"
	"net/textproto"
	"net/url"
	"reflect"
	"strings"
	"time"
)

type requestBinding struct{}

func (requestBinding) Name() string {
	return "request"
}

type scanner func(req *http.Request, target reflect.Value)

var requestScanners = map[string]scanner{}

func init() {
	requestScanners["method"] = func(req *http.Request, target reflect.Value) {
		target.SetString(req.Method)
	}
	requestScanners["url"] = func(req *http.Request, target reflect.Value) {
		dst, ok := target.Addr().Interface().(**url.URL)
		if !ok {
			return
		}
		*dst = req.URL
	}
	requestScanners["proto"] = func(req *http.Request, target reflect.Value) {
		target.SetString(req.Proto)
	}
	requestScanners["protomajor"] = func(req *http.Request, target reflect.Value) {
		target.SetInt(int64(req.ProtoMajor))
	}
	requestScanners["protominor"] = func(req *http.Request, target reflect.Value) {
		target.SetInt(int64(req.ProtoMinor))
	}
	requestScanners["header"] = func(req *http.Request, target reflect.Value) {
		dst, ok := target.Addr().Interface().(*http.Header)
		if !ok {
			return
		}
		*dst = req.Header
	}
	requestScanners["contentlength"] = func(req *http.Request, target reflect.Value) {
		target.SetInt(req.ContentLength)
	}
	requestScanners["transferencoding"] = func(req *http.Request, target reflect.Value) {
		dst, ok := target.Addr().Interface().(*[]string)
		if !ok {
			return
		}
		*dst = req.TransferEncoding
	}
	requestScanners["close"] = func(req *http.Request, target reflect.Value) {
		target.SetBool(req.Close)
	}
	requestScanners["host"] = func(req *http.Request, target reflect.Value) {
		target.SetString(req.Host)
	}
	requestScanners["form"] = func(req *http.Request, target reflect.Value) {
		dst, ok := target.Addr().Interface().(*url.Values)
		if !ok {
			return
		}
		*dst = req.Form
	}
	requestScanners["postform"] = func(req *http.Request, target reflect.Value) {
		dst, ok := target.Addr().Interface().(*url.Values)
		if !ok {
			return
		}
		*dst = req.PostForm
	}
	requestScanners["multipartform"] = func(req *http.Request, target reflect.Value) {
		dst, ok := target.Addr().Interface().(**multipart.Form)
		if !ok {
			return
		}
		*dst = req.MultipartForm
	}
	requestScanners["trailer"] = func(req *http.Request, target reflect.Value) {
		dst, ok := target.Addr().Interface().(*http.Header)
		if !ok {
			return
		}
		*dst = req.Trailer
	}
	requestScanners["remoteaddr"] = func(req *http.Request, target reflect.Value) {
		target.SetString(req.RemoteAddr)
	}
	requestScanners["requesturi"] = func(req *http.Request, target reflect.Value) {
		target.SetString(req.RequestURI)
	}
	requestScanners["tls"] = func(req *http.Request, target reflect.Value) {
		dst, ok := target.Addr().Interface().(**tls.ConnectionState)
		if !ok {
			return
		}
		*dst = req.TLS
	}
	requestScanners["cancel"] = func(req *http.Request, target reflect.Value) {
		dst, ok := target.Addr().Interface().(*<-chan struct{})
		if !ok {
			return
		}
		*dst = req.Cancel
	}
	requestScanners["response"] = func(req *http.Request, target reflect.Value) {
		dst, ok := target.Addr().Interface().(**http.Response)
		if !ok {
			return
		}
		*dst = req.Response
	}
}

func mapHeader(obj interface{}, header http.Header) error {
	sinfo := scache.get(reflect.TypeOf(obj))
	val := reflect.ValueOf(obj).Elem()
	for i, fd := range sinfo.field {
		typeField := fd.tp
		structField := val.Field(i)
		if !structField.CanSet() {
			continue
		}

		tag := fd.tag("header")
		structFieldKind := structField.Kind()
		inputFieldName := tag.name
		if inputFieldName == "" {
			inputFieldName = typeField.Name

			// if "header" tag is nil, we inspect if the field is a struct.
			// this would not make sense for JSON parsing but it does for a form
			// since data is flatten
			if structFieldKind == reflect.Struct {
				err := mapHeader(structField.Addr().Interface(), header)
				if err != nil {
					return err
				}
				continue
			}
		}

		dv := fd.defaultValue("header")
		inputValue, exists := headerGet(header, inputFieldName)
		if !exists {
			// Set the field as default value when the input value is not exist
			if dv.hasDefault {
				structField.Set(dv.value)
			}
			continue
		}
		// Set the field as default value when the input value is empty
		if dv.hasDefault && inputValue[0] == "" {
			structField.Set(dv.value)
			continue
		}
		if _, isTime := structField.Interface().(time.Time); isTime {
			if err := setTimeField(inputValue[0], typeField, structField); err != nil {
				return err
			}
			continue
		}
		if err := setWithProperType(typeField.Type.Kind(), inputValue, structField, tag.option); err != nil {
			return err
		}
	}

	return nil
}

func mapRequest(obj interface{}, req *http.Request) error {
	sinfo := scache.get(reflect.TypeOf(obj))
	val := reflect.ValueOf(obj).Elem()
	for i, fd := range sinfo.field {
		typeField := fd.tp
		structField := val.Field(i)
		if !structField.CanSet() {
			continue
		}

		tag := fd.tag("request")
		structFieldKind := structField.Kind()
		inputFieldName := tag.name
		if inputFieldName == "" {
			inputFieldName = typeField.Name
			if structFieldKind == reflect.Struct {
				err := mapRequest(structField.Addr().Interface(), req)
				if err != nil {
					return err
				}
				continue
			}
		}
		scanner, ok := requestScanners[strings.ToLower(inputFieldName)]
		if !ok {
			continue
		}
		scanner(req, structField)
	}
	return nil
}

func (r requestBinding) Bind(req *http.Request, obj interface{}) error {
	if err := mapHeader(obj, req.Header); err != nil {
		return err
	}
	if err := mapRequest(obj, req); err != nil {
		return err
	}
	return validate(obj)
}

func headerGet(h http.Header, key string) ([]string, bool) {
	if h == nil {
		return nil, false
	}
	mh := textproto.MIMEHeader(h)
	v, ok := mh[textproto.CanonicalMIMEHeaderKey(key)]
	return v, ok
}
